﻿using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;

namespace Blob_Watcher
{
    public delegate void FoundLogDelegate(CloudBlockBlob LogBlob);

    public class LogScanner
    {
        private CloudBlobClient blobClient; 
        private CloudBlobContainer logContainer;
        private Gatekeeper gatekeeper; 

        private FoundLogDelegate callback;
        private System.Timers.Timer scanTimer = new System.Timers.Timer();

        public LogScanner(CloudBlobClient blobClient, Gatekeeper gateKeeper)
        {
            // use client to create our container reference
            this.blobClient = blobClient;
            this.logContainer = blobClient.GetContainerReference("$logs");

            // save the gatekeeper
            this.gatekeeper = gateKeeper;

            // default to scanning every 5 minutes
            ScanInterval = new TimeSpan(0, 5, 0);
            scanTimer.Elapsed += ScanLogsEvent;
        }

        // used to override the default scan interval
        public TimeSpan ScanInterval { get; set; }

        /// <summary>
        /// Do an initial scan, and try to scan again at regular interval
        /// </summary>
        /// <param name="callback">A delegate to be called when a new log file is found.</param>
        public void Start(FoundLogDelegate callback)
        {
            Console.WriteLine("Starting Log scanner");

            this.callback = callback; // save our callback for later...

            // this will also be done by ScanLogsEvent, but wanted to demo the start
            this.gatekeeper.Start(); 

            // call first time so we can get going
            // subsequent calls will be done by the timer... 
            ScanLogsEvent(null, null); // do a scan

            scanTimer.Interval = ScanInterval.TotalMilliseconds;
            scanTimer.Start(); // start the timer
        }

        /// <summary>
        /// Stop scanning for updates
        /// </summary>
        public void Stop()
        {
            Console.WriteLine("Starting Log scanner");

            scanTimer.Stop(); // stop scanning
            this.gatekeeper.Stop();
        }

        /// <summary>
        /// if we have control via gatekeeper, scan the storage logs for new log files
        /// Called on start and by scan timer...
        /// </summary>
        private void ScanLogsEvent(Object source, ElapsedEventArgs e)
        {
            Console.WriteLine("Scanning for new logs");

            scanTimer.Enabled = false; // stop raising events while this is running...

            double ScanPasses = 1; // do at least one pass
            string startingPrefix = string.Empty; // log prefix starting point
            bool pastPreviousLog = true; // have we passed our previous "last log" file?

            // attempt to acquire lease on gatekeeper object... 
            if (gatekeeper.HasLease(true)) // if we have the gatekeeper lease
            {
                // get last log file value from gatekeeper
                string lastLog = gatekeeper.GetText();

                // calculate starting prefix
                if (!lastLog.Equals("blob/")) // we had a "last log" from previous runs
                {
                    startingPrefix = getPrefixFromURI(lastLog); // use that prefix as our starting point
                    ScanPasses = ((DateTime.UtcNow - PrefixToDateTime(startingPrefix)).TotalHours + 1); // 
                    pastPreviousLog = false; // don't start raising "found log" events until we're past the last processed log
                }

                // if we have previous scans (from gatekeeper), we'll scan each hour since the last found log
                //TODO: need to optimize and update gatekeeper with new timeframe so we don't keep scanning past hours
                while (ScanPasses >= 1)
                {
                    // List the blobs using the prefix
                    IEnumerable<IListBlobItem> blobs = 
                        logContainer.ListBlobs(string.Format("blob/{0}", startingPrefix), true, BlobListingDetails.Metadata, null);

                    // interate the list of log files
                    foreach (IListBlobItem item in blobs)
                    {
                        CloudBlockBlob log = item as CloudBlockBlob;
                        string LogURI = log.Uri.ToString();
                        if (log != null)
                        {
                            if (pastPreviousLog)
                            {
                                // call Delegate to act on log file
                                this.callback(log);

                                // update gatekeeper blob 
                                gatekeeper.SetText(LogURI);
                                lastLog = LogURI;
                            }
                            if (lastLog.Equals(LogURI, StringComparison.OrdinalIgnoreCase))
                                pastPreviousLog = true;
                        }
                    }

                    // get next prefix and search again... 
                    startingPrefix = incrementPrefix(startingPrefix);
                    ScanPasses--; // decrement pass count
                }
            }

            scanTimer.Enabled = true; // start raising events again
        }

        private string getPrefixFromURI(string uri)
        {
            int startPOS = uri.IndexOf("blob/") + 5;
            int endPOS = uri.LastIndexOf('/');

            return uri.Substring(startPOS, endPOS - startPOS);
        }

        private DateTime PrefixToDateTime(string fileprefix)
        {
            DateTime tmpDT;
            // convert prefix to datetime
            DateTime.TryParseExact(fileprefix, "yyyy/MM/dd/HHmm", null,
                                   DateTimeStyles.None, out tmpDT);
            return tmpDT;
        }

        /// <summary>
        /// This method will take a prefix, and determine the next available prefix
        /// </summary>
        private string incrementPrefix(string fileprefix)
        {
            // convert prefix to datetime
            DateTime tmpDT = PrefixToDateTime(fileprefix);
            // increment to next available hour
            tmpDT = tmpDT.AddHours(1);
            // format string using updated datatime and return it
            return String.Format("{0:yyyy/MM/dd/HHmm}", tmpDT);  
        }
    }
}
